package com.easylinkin.linkappapi.facerecognition.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.easylinkin.linkappapi.common.model.RequestModel;
import com.easylinkin.linkappapi.facerecognition.entity.FacePhoto;
import com.easylinkin.linkappapi.facerecognition.entity.Personnel;
import com.easylinkin.linkappapi.facerecognition.entity.PersonnelIdentifier;
import com.easylinkin.linkappapi.facerecognition.service.FacePhotoService;
import com.easylinkin.linkappapi.facerecognition.service.PersonnelIdentifierService;
import com.easylinkin.linkappapi.facerecognition.service.ai.FaceRecognitionService;
import com.easylinkin.linkappapi.facerecognition.service.impl.PersonnelServiceImpl;
import com.easylinkin.linkappapi.facerecognition.service.oss.ObjectStorageOutsideService;
import com.easylinkin.linkappapi.security.context.LinkappUserContextProducer;
import com.easylinkin.linkappapi.security.entity.LinkappUser;
import com.google.gson.JsonObject;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import site.morn.rest.RestBuilders;
import site.morn.rest.RestMessage;

/**
 * 提供人员管理接口
 */
@RestController
@RequestMapping("/personnel")
public class PersonnelController {

    private static final Logger log = LoggerFactory.getLogger(PersonnelController.class);

    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
    private static Pattern PATTERN = Pattern.compile("^[0-9a-zA-Z_]{1,}$");

    private static Pattern fileNamePattern = null;

    @Value("${oss.bucket}")
    private String bucket;

    @Value("${oss.dir}")
    private String dir;

    @Value(("${ai.face.platform}"))
    private String platform;

    @Autowired
    @Qualifier("oss")
    private ObjectStorageOutsideService ossClient;

    @Resource
    private LinkappUserContextProducer linkappUserContextProducer;

    @Autowired
    private FaceRecognitionService faceRecognitionService;

    @Autowired
    private PersonnelServiceImpl personnelService;

    @Autowired
    private PersonnelIdentifierService personnelIdentifierService;

    @Autowired
    private FacePhotoService facePhotoService;

    /**
     * 查询人员列表
     */
    @PostMapping("/page")
    public RestMessage queryPersonnel(@RequestBody RequestModel<Personnel> requestModel) {
        IPage<Personnel> record = personnelService.queryPersonnelList(requestModel.getPage(), requestModel.getCustomQueryParams());
        record.getRecords().forEach(this::checkPersonnelPhotoInfo);
        record.getRecords().forEach(p -> {
            PersonnelIdentifier identifier = personnelIdentifierService.getById(p.getIdentifierId());
            if (identifier != null) {
                if (identifier.getDeleted()) {
                    p.setIdentifierName("");
                } else {
                    p.setIdentifierName(identifier.getIdentifier() );
                }
                p.setColor(identifier.getColor());
            }
        });
        return RestBuilders.successBuilder().data(record).build();
    }

    @PostMapping("/update")
    public RestMessage updatePersonnel(@RequestPart("photos") MultipartFile[] photos, @Validated Personnel personnel) {

        if (personnel.getId() == null) {
            return RestBuilders.failureBuilder().message("人员ID为空").build();
        }
        Personnel currentPersonnel = personnelService.getById(personnel.getId());
        if (currentPersonnel == null) {
            return RestBuilders.failureBuilder().message("人员不存在，ID " + personnel.getId()).build();
        }

        Personnel oldPersonnel = personnelService.queryWithPersonnelName(personnel.getName());
        if (oldPersonnel != null && !oldPersonnel.getId().equals(personnel.getId())) {
            return RestBuilders.failureBuilder().message("人员名字已存在").build();
        }

        LinkappUser currentUser = linkappUserContextProducer.getCurrent();

        // 1. 照片存储到OSS
        List<String> photoUrls = new ArrayList<String>();
        if (personnel.getPhotoList() != null) {
            photoUrls.addAll(personnel.getPhotoList());
        }
        Map<String, String> fileToPhotoUrls = new HashMap<String, String>();
        for (MultipartFile photo : photos) {
            Optional<String> fileName = generateFileName(photo.getOriginalFilename(), currentUser.getUsername());
            if (!fileName.isPresent()) {
                return RestBuilders.failureBuilder().message("请检查上传照片的名字").build();
            }

            try {
                ossClient.uploadImgObject(bucket, fileName.get(), photo.getInputStream());

                String url = ossClient.generatePresignedUrl(bucket, fileName.get(), 7 * 24 * 60 * 60);
                photoUrls.add(url);
                fileToPhotoUrls.put(photo.getOriginalFilename(), url);
            } catch (IOException e) {
                log.error("upload image to oss error {}", e.getMessage());
                return RestBuilders.failureBuilder().message("保存人员照片失败").build();
            }
        }

        // 2. 在第三方平台更新用户

        Optional<JsonObject> platformResponse = faceRecognitionService.updateUser(personnel.getName(), currentPersonnel.getSubjectId(), "", photos, personnel.getPhotoList(), fileToPhotoUrls);

        if ("koala".equalsIgnoreCase(platform)) { // 处理考拉
            if (!platformResponse.isPresent()) {
                return RestBuilders.failureBuilder().message("access koala error").build();
            }
            if (platformResponse.get().get("code").getAsInt() != 0) {
                return RestBuilders.failureBuilder().message("Koala error: " + platformResponse.get().get("desc").getAsString()).build();
            }
        } else { // 处理百度
            if (platformResponse.isPresent()) {
                return RestBuilders.failureBuilder().message(platformResponse.get().get("desc").getAsString()).build();
            }
            personnel.setSubjectId(linkappUserContextProducer.getCurrent().getTenantId() + "_" + personnel.getName());
        }
        // 3. 创建AEP用户
        personnel.setPhotoUrl(String.join(",", photoUrls));

        if (!personnelService.updateById(personnel)) {
            return RestBuilders.failureBuilder().message("保存人员信息失败").build();
        }

        return RestBuilders.successBuilder().build();
    }

    @DeleteMapping("/{id}")
    public RestMessage deletePersonnel(@PathVariable("id") Long id) {

        Personnel personnel = personnelService.getById(id);
        if (personnel == null || !personnel.getTenantId().equalsIgnoreCase(linkappUserContextProducer.getCurrent().getTenantId())) {
            return RestBuilders.failureBuilder().message("无法删除该人员").build();
        }
        boolean deleteThirdParty = faceRecognitionService.deleteUser(personnel.getSubjectId());

        if (!deleteThirdParty) {
            return RestBuilders.failureBuilder().message("第三方平台用户删除失败").build();
        }
        boolean deleteSuccess = personnelService.removeById(id);
        if (!deleteSuccess) {
            return RestBuilders.failureBuilder().message("删除失败").build();
        }

        return RestBuilders.successBuilder().build();
    }

    @PostMapping("/batch/delete")
    public RestMessage batchDeleteCamera(@RequestParam("ids") String ids) {
        if (ids == null || ids.isEmpty()) {
            return RestBuilders.successBuilder().build();
        }

        List<String> personnelIds = Arrays.asList(ids.split(","));

        String currentTenantId = linkappUserContextProducer.getCurrent().getTenantId();
        List<Long> deniedIds = personnelService.listByIds(personnelIds).stream()
                .filter(r -> !r.getTenantId().equalsIgnoreCase(currentTenantId))
                .mapToLong(Personnel::getId)
                .boxed()
                .collect(Collectors.toList());

        if (deniedIds.size() != 0) {
            return RestBuilders.failureBuilder().message("无权删除").data(deniedIds).build();
        }

        StringBuilder sb = new StringBuilder();
        for (String id: personnelIds) {
            Personnel personnel = personnelService.getById(id);
            boolean deleteThirdParty = faceRecognitionService.deleteUser(personnel.getSubjectId());
            if (!deleteThirdParty) {
                sb.append(id).append(",");
            }
        }

        String tip = "";
        if (!sb.toString().isEmpty()) {
            tip = "第三方平台删除失败 ID " + sb.toString();
        }

        if (!personnelService.removeByIds(personnelIds)) {
            return RestBuilders.failureBuilder().message("批量删除人员失败").build();
        }

        return RestBuilders.successBuilder().message(tip).build();
    }

    /**
     * 因为存储在OSS中的照片存在过期时间，需要在返回给客户端之前对链接进行检查。
     * 如果当前链接已过期，则重新生成链接地址
     */
    private void checkPersonnelPhotoInfo(Personnel personnel) {
        String photoUrl = personnel.getPhotoUrl();
        if (photoUrl != null && !photoUrl.isEmpty()) {
            List<String> photos = Arrays.asList(photoUrl.split(","));
            List<String> newPhotoUrls = new ArrayList<>();
            boolean hasPhotoExpired = false;
            for (String photo : photos) {
                if (isExpired(photo)) {

                    String s = null;
                    try {
                        s = reGenerateOssUrl(URLDecoder.decode(photo, "utf-8"));
                    } catch (UnsupportedEncodingException e) {
                        log.error("decode photo url {} error {}", photo, e.getMessage());
                    }
                    newPhotoUrls.add(s);
                    hasPhotoExpired = true;

                    FacePhoto facePhoto = facePhotoService.queryPhotoWithUrl(photo);
                    if (Objects.nonNull(facePhoto)){
                        facePhoto.setPhotoUrl(s);
                        facePhotoService.updateById(facePhoto);
                    }
                } else {
                    newPhotoUrls.add(photo);
                }
            }
            if (hasPhotoExpired) {
                personnel.setPhotoUrl(String.join(",", newPhotoUrls));
                personnelService.updateById(personnel);
            }
            personnel.setPhotoList(newPhotoUrls);
        }
    }

    /**
     * 创建人员
     * <p>
     * 1. 照片存储到OSS
     * 2. 在第三方平台创建人脸底库
     * 3. 记录AEP的人员信息
     */
    @PostMapping
    public RestMessage createPersonnel( @RequestPart("photos") MultipartFile[] photos, @Validated Personnel personnel) {

        if (photos.length == 0) {
            return RestBuilders.failureBuilder().message("需要携带人脸识别照片").build();
        }

        if (photos.length > 3) {
            return RestBuilders.failureBuilder().message("人脸识别照片最多只能上传3张").build();
        }

        if (personnelService.queryWithPersonnelName(personnel.getName()) != null) {
            return RestBuilders.failureBuilder().message("人员名字已存在").build();
        }

        LinkappUser currentUser = linkappUserContextProducer.getCurrent();

        // 1. 照片存储到OSS
        List<String> photoUrls = new ArrayList<String>();
        Map<String, String> fileToPhotoUrls = new HashMap<String, String>();
        for (MultipartFile photo : photos) {
            Optional<String> fileName = generateFileName(photo.getOriginalFilename(), currentUser.getUsername());
            if (!fileName.isPresent()) {
                return RestBuilders.failureBuilder().message("请检查上传照片的名字").build();
            }

            try {
                ossClient.uploadImgObject(bucket, fileName.get(), photo.getInputStream());

                String url = ossClient.generatePresignedUrl(bucket, fileName.get(), 7 * 24 * 60 * 60);
                photoUrls.add(url);
                fileToPhotoUrls.put(photo.getOriginalFilename(), url);
            } catch (IOException e) {
                log.error("upload image to oss error {}", e.getMessage());
                return RestBuilders.failureBuilder().message("保存人员照片失败").build();
            }
        }

        // 2. 在第三方平台创建用户
        String name = personnel.getName();
        if ("baidu".equalsIgnoreCase(platform)) {
            if (!PATTERN.matcher(name).find()) {
                return RestBuilders.failureBuilder().message("用户名只支持字母、数字、下划线").build();
            }

            name = linkappUserContextProducer.getCurrent().getTenantId() + "_" + personnel.getName();
        }
        Optional<JsonObject> platformResponse = faceRecognitionService.registerUser(name, photos, new ArrayList<>(), fileToPhotoUrls);
        if ("koala".equalsIgnoreCase(platform)) { // 处理考拉
            if (!platformResponse.isPresent()) {
                return RestBuilders.failureBuilder().message("access koala error").build();
            }
            if (platformResponse.get().get("code").getAsInt() != 0) {
                return RestBuilders.failureBuilder().message("Koala error: " + platformResponse.get().get("desc").getAsString()).build();
            }

            // 3. 创建AEP用户
            personnel.setSubjectId(platformResponse.get().get("data").getAsJsonObject().get("id").getAsString()); // 获取第三方平台的用户ID
        } else { // 处理百度
            if (platformResponse.isPresent()) {

                return RestBuilders.failureBuilder().message(platformResponse.get().get("error_msg").getAsString()).build();
            }

            // 3. 创建AEP用户
            personnel.setSubjectId(name); // 获取第三方平台的用户ID
        }

        personnel.setTenantId(currentUser.getTenantId());
        personnel.setPhotoUrl(String.join(",", photoUrls));

        if (!personnelService.save(personnel)) {
            return RestBuilders.failureBuilder().message("保存人员信息失败").build();
        }

        return RestBuilders.successBuilder().build();
    }

    /**
     * 重新生成图片的OSS存储链接地址。
     * <p>
     * 若重新生成失败，则返回之前的地址。
     */
    private String reGenerateOssUrl(String originalUrl) {
        if (fileNamePattern == null) {
            String pattern = "/(" + dir + ".*)\\?"; //取出bucket 到 ? 之间的内容
            fileNamePattern = Pattern.compile(pattern);
        }

        Matcher matcher = fileNamePattern.matcher(originalUrl);
        if (matcher.find()) {
            String originalFilename = matcher.group(1);
            return ossClient.generatePresignedUrl(bucket, originalFilename, 7 * 24 * 60 * 60);
        }

        return originalUrl;
    }

    /*
     * 生成的文件路径规则：配置的dir + "/face/" + 用户名 + "年月日时分秒" + .照片后缀
     * */
    private Optional<String> generateFileName(String originalFilename, String userName) {

        // 取最后一个 . 后面的部分作为照片的后缀
        String suffix = "";
        try {
            String[] parts = originalFilename.split("\\.");
            int idx = parts.length - 1;
            suffix = parts[idx];
        } catch (Exception e) {
            log.error("extract image suffix error {}", e.getMessage());
            return Optional.empty();
        }

        String filename = "";
        if (dir.endsWith("/")) {
            filename += (dir + "face/" + userName + "/");
        } else {
            filename += (dir + "/face/" + userName + "/");
        }
        filename += (sdf.format(new Date()) + "." + suffix);

        return Optional.of(filename);
    }

    /**
     * 检查生成的连接是否已过期
     */
    private boolean isExpired(String ossUrl) {
        String[] segments = ossUrl.split("\\?")[1].split("&");
        Instant xDate = null;
        long expireSeconds = 0;
        for (String segment : segments) {

            // minio 的过期时间点
            if (segment.startsWith("X-Amz-Date")) {
                DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
                try {
                    xDate = Instant.ofEpochMilli(dateFormat.parse(segment.split("=")[1]).getTime());
                } catch (ParseException e) {
                    log.error("Parse koala expire date {} error {}", segment.split("=")[1], e.getMessage());
                    return true;
                }
            }
            if (segment.startsWith("X-Amz-Expires")) {
                expireSeconds = Long.parseLong(segment.split("=")[1]);
            }

            // 阿里链接的过期时间点
            if (segment.startsWith("Expires")) {
                xDate = Instant.ofEpochSecond(Long.parseLong(segment.split("=")[1]));
            }
        }

        if (xDate == null) {
            return true;
        }

        return Instant.now().isAfter(xDate.plusSeconds(expireSeconds));
    }
}
